Objectives
What are the 100 highest total wage zip codes? lowest? what are the 100 highest average wage zip codes? lowest? how does the 100 total wages of the zip codes differ from the 100 average wages of the zipcodes? top 3 states with the highest/lowest wages? Make clear concise graphs and compare to other types of existing data Create a machine learning model to predict future data
Load The Neccesary libraries
library(tidyverse)
library(maps)
library(mapdata)
library(plotly)
library(caTools)
library(reshape2)
Examine data
wages <- read_csv("free-zipcode-database.csv")
Rows: 81831 Columns: 20
-- Column specification ---------------------------------------------------------------------------------------------------
Delimiter: ","
chr (10): Zipcode, ZipCodeType, City, State, LocationType, WorldRegion, Country, LocationText, Location, Notes
dbl (9): RecordNumber, Lat, Long, Xaxis, Yaxis, Zaxis, TaxReturnsFiled, EstimatedPopulation, TotalWages
lgl (1): Decommisioned
i Use `spec()` to retrieve the full column specification for this data.
i Specify the column types or set `show_col_types = FALSE` to quiet this message.
glimpse(wages)
Rows: 81,831
Columns: 20
$ RecordNumber <dbl> 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22, 23, 24, 25, 26~
$ Zipcode <chr> "00704", "00704", "00704", "00704", "00704", "00704", "00704", "00704", "00705", "00705", "00~
$ ZipCodeType <chr> "STANDARD", "STANDARD", "STANDARD", "STANDARD", "STANDARD", "STANDARD", "STANDARD", "STANDARD~
$ City <chr> "PARC PARQUE", "PASEO COSTA DEL SUR", "SECT LANAUSSE", "URB EUGENE RICE", "URB GONZALEZ", "UR~
$ State <chr> "PR", "PR", "PR", "PR", "PR", "PR", "PR", "PR", "PR", "PR", "PR", "PR", "PR", "PR", "PR", "PR~
$ LocationType <chr> "NOT ACCEPTABLE", "NOT ACCEPTABLE", "NOT ACCEPTABLE", "NOT ACCEPTABLE", "NOT ACCEPTABLE", "NO~
$ Lat <dbl> 17.96, 17.96, 17.96, 17.96, 17.96, 17.96, 17.96, 17.96, 18.14, 18.14, 18.14, 18.14, 18.14, 18~
$ Long <dbl> -66.22, -66.22, -66.22, -66.22, -66.22, -66.22, -66.22, -66.22, -66.26, -66.26, -66.26, -66.2~
$ Xaxis <dbl> 0.38, 0.38, 0.38, 0.38, 0.38, 0.38, 0.38, 0.38, 0.38, 0.38, 0.38, 0.38, 0.38, 0.38, 0.38, 0.3~
$ Yaxis <dbl> -0.87, -0.87, -0.87, -0.87, -0.87, -0.87, -0.87, -0.87, -0.86, -0.86, -0.86, -0.86, -0.86, -0~
$ Zaxis <dbl> 0.30, 0.30, 0.30, 0.30, 0.30, 0.30, 0.30, 0.30, 0.31, 0.31, 0.31, 0.31, 0.31, 0.31, 0.31, 0.3~
$ WorldRegion <chr> NA, NA, NA, NA, NA, NA, NA, NA, NA, NA, NA, NA, NA, NA, NA, NA, NA, NA, NA, NA, NA, NA, NA, N~
$ Country <chr> "US", "US", "US", "US", "US", "US", "US", "US", "US", "US", "US", "US", "US", "US", "US", "US~
$ LocationText <chr> "Parc Parque, PR", "Paseo Costa Del Sur, PR", "Sect Lanausse, PR", "Urb Eugene Rice, PR", "Ur~
$ Location <chr> "NA-US-PR-PARC PARQUE", "NA-US-PR-PASEO COSTA DEL SUR", "NA-US-PR-SECT LANAUSSE", "NA-US-PR-U~
$ Decommisioned <lgl> FALSE, FALSE, FALSE, FALSE, FALSE, FALSE, FALSE, FALSE, FALSE, FALSE, FALSE, FALSE, FALSE, FA~
$ TaxReturnsFiled <dbl> NA, NA, NA, NA, NA, NA, NA, NA, NA, NA, NA, NA, NA, NA, NA, NA, NA, NA, NA, NA, NA, NA, NA, N~
$ EstimatedPopulation <dbl> NA, NA, NA, NA, NA, NA, NA, NA, NA, NA, NA, NA, NA, NA, NA, NA, NA, NA, NA, NA, NA, NA, NA, N~
$ TotalWages <dbl> NA, NA, NA, NA, NA, NA, NA, NA, NA, NA, NA, NA, NA, NA, NA, NA, NA, NA, NA, NA, NA, NA, NA, N~
$ Notes <chr> NA, NA, NA, NA, NA, NA, NA, NA, NA, NA, NA, NA, NA, NA, NA, NA, NA, NA, NA, NA, NA, NA, NA, N~
Looking at United States only
wages <- wages %>% filter(Country == "US")
Removing unwanted variables
wages <- wages %>% select(-RecordNumber,-Xaxis,-Yaxis,-Zaxis,-Location,-WorldRegion,-LocationText,-LocationType,-Country,-Decommisioned,-Notes)
Removing Unwanted cases like puerto rico and NA values and duplicates
unique(wages$State)
[1] "PR" "NJ" "NY" "VI" "MA" "ME" "NH" "VT" "CT" "RI" "DE" "PA" "WV" "KY" "TN" "VA" "GA" "IN" "OH" "IL" "IA" "MN" "WI"
[24] "MT" "ND" "SD" "KS" "MO" "NE" "CO" "WY" "ID" "UT" "AZ" "NM" "TX" "CA" "NV" "OR" "WA" "AK" "GU" "HI" "AS" "PW" "FM"
[47] "MP" "MH" "FL" "SC" "AL" "MS" "LA" "AR" "OK" "MI" "DC" "MD" "NC" "AE" "AA" "AP"
wages <- wages %>% filter(State != "PR")
for(i in 1:9){
wages<- wages %>% filter(!is.na(wages[i]))
}
wages <- wages %>% distinct(Zipcode, .keep_all = TRUE)
We see a loss of about 50,000 values in the data set. About 25,000 lost from blank values and another 25,000 from duplicates. Since this is the majority of the data set, it is not a very clean data set and may not have a very accurate representation of the initial data. However, I will still plan to analyse the data in a comprehensive way in order to find answers to the proposed questions.
EXporting a csv file in order to use the cleaned data in other programs i.e. Tableau
write.table(wages,file = "CleanWages.csv",row.names = F,sep = ",")
What is the 100 highest wage zip codes?
options(scipen = 999)
highestwage <- wages %>% arrange(-TotalWages)
highestwage <- highestwage %>% mutate(rank = 1:28844)
highestwage <- highestwage %>% filter(rank <= 100)
print(head(highestwage))
highestwage %>% ggplot(aes(x = State,fill = State)) + geom_bar() + labs(title = "Number of High Wage Zip Codes Per State in the Top 100" ,x = "State",y = "Frequency")

Looking at the graph of each state in the top 100 and the Total wages of the zip codes in each state, it is easy to see that California, New York, Texas, and Illinois contribute the majority of the zip codes with the highest wages.
options(scipen = 999)
highestwage %>% ggplot(aes(x = TotalWages/1000000000)) + geom_density() + labs(title = "Density of Total Wages in the Top 100" ,x = "Total Wages (Billion $)",y = "Density")

By taking a look at the graph of the density of each price we can see that the majority of zip codes in the top 100 highest wages are around 1.7 billion dollars. Also as you get past 1.9 billion dollars the amount of zip codes decreases steadily.
What is the 100 lowest wage zip codes?
lowestwage <- wages %>% arrange(TotalWages)
lowestwage <- lowestwage %>% mutate(rank = 1:28844)
lowestwage <- lowestwage %>% filter(rank <= 100)
print(head(lowestwage))
lowestwage %>% ggplot(aes(x = State,fill = State)) + geom_bar() + labs(title = "Number of Low Wage Zip Codes Per State in the Bottom 100" ,x = "State",y = "Frequency")

Here we see that Michigan, Arizona, and Texas are the 3 most frequent states in the Bottom 100.
options(scipen = 999)
lowestwage %>% ggplot(aes(x = TotalWages/1000000)) + geom_density() + labs(title = "Density of Total Wages in the Bottom 100" ,x = "Total Wages (Million $)",y = "Density")

Similar to the last density chart, this graph predicts the majority of wages in the bottom 100 are around $4.5 million.
What is the 100 highest average wage zip codes?
highestavgwage <- wages %>% arrange(-averageWage)
highestavgwage <- highestavgwage %>% mutate(rank = 1:28844)
highestavgwage <- highestavgwage %>% filter(rank <= 100)
print(head(highestavgwage))
highestavgwage %>% ggplot(aes(x = State,fill = State)) + geom_bar() + labs(title = "Number of Average Wage Zip Codes Per State in the Top 100" ,x = "State",y = "Frequency")

The average zip code wage in each state shows a different story than the total wages in each zip code. The two states that make up the majority are New York and California, where New York is much more frequent.
options(scipen = 999)
highestavgwage %>% ggplot(aes(x = averageWage/1000)) + geom_density() + labs(title = "Density of Average Wages in the Top 100" ,x = "Total Wages (Thousand $)",y = "Density")

The densities show that the majority of the average zip code wages in the top 100 are around 190 thousand dollars.
What is the 100 lowest average wage zip codes?
lowestavgwage <- wages %>% arrange(averageWage)
lowestavgwage <- lowestavgwage %>% mutate(rank = 1:28844)
lowestavgwage <- lowestavgwage %>% filter(rank <= 100)
print(head(lowestavgwage))
lowestavgwage %>% ggplot(aes(x = State,fill = State)) + geom_bar() + labs(title = "Number of Average Wage Zip Codes Per State in the Bottom 100" ,x = "State",y = "Frequency")

Similar to the lowest total wages, Michigan and Arizona are the highest contributors to the bottom 100 average zip code wages.
options(scipen = 999)
lowestavgwage %>% ggplot(aes(x = averageWage/1000)) + geom_density() + labs(title = "Density of Average Wages in the Bottom 100" ,x = "Total Wages (Thousand $)",y = "Density")

Here the majority of the average wages in the bottom 100 are around 7.5 thousand dollars.
What are the 3 states with the highest wages by total?
statewage <- wages %>% group_by(State) %>% summarise(
totalstatewages = sum(TotalWages))
highstatewage <- statewage %>% arrange(-totalstatewages)
highstatewage <- highstatewage %>% mutate(rank = 1:51)
highstatewage <- highstatewage %>% filter(rank <= 3)
print(head(highstatewage))
These results support the previous conclusion from Graph 1 where we saw that California Texas and New York were among the main contributors of the highest state wages. However these results give us further insight that Illinois is not actually among the top 3 highest wage states when looking at the total wages
What are the 3 states with the lowest wages by total?
lowstatewage <- statewage %>% arrange(totalstatewages)
lowstatewage <- lowstatewage %>% mutate(rank = 1:51)
lowstatewage <- lowstatewage %>% filter(rank <= 3)
print(head(lowstatewage))
What does the average of each state look like?
state_avg_wage <- wages %>%
group_by(State) %>%
summarise(avgstatewages = mean(TotalWages))
plot_geo(data = state_avg_wage,
locationmode = 'USA-states') %>%
add_trace(locations = ~State,
z = ~state_avg_wage$avgstatewages,
zmin = min(state_avg_wage$avgstatewages),
zmax = max(state_avg_wage$avgstatewages),
color = state_avg_wage$avgstatewages) %>%
layout(geo = list(scope= 'usa'),
title = "\nAverage Wages in the United States by State") %>% colorbar(tickprefix = "$")
The graph above shows that out of all the states, the highest wage location is Washington DC with an average wage of about $550 Million. The second two locations are California and New Jersey by a $150 million wage gap. In comparison to all other Locations, these three stand out as states and territories with high wages.
Importing Other Datasets From Online For Further Analysis
CostOfLiving <- read.csv("Cost of Living.csv")
StateAbrev <- read.csv("StateAbrev.csv")
Preparing Data For Joining
StateAbrev <- StateAbrev %>% rename(State = USPS.Abbreviation)
CostOfLiving <- CostOfLiving %>% rename(State.Name = State)
Joining the Datasets
CostOfLiving <- CostOfLiving %>% full_join(StateAbrev, by = "State.Name")
State_avg_vs_COL <- state_avg_wage %>% full_join(CostOfLiving, by = "State")
State_avg_vs_COL <- State_avg_vs_COL %>% select(-Rank,-State.Name)
Working With the New Dataset To Determine Potential Relationships
plot_geo(data = State_avg_vs_COL,
locationmode = 'USA-states') %>%
add_trace(locations = ~State,
z = ~State_avg_vs_COL$Index,
zmin = min(State_avg_vs_COL$Index),
zmax = max(State_avg_vs_COL$Index),
color = State_avg_vs_COL$Index) %>%
layout(geo = list(scope= 'usa'),
title = "\nCost of living Index in the United States by State")
Upon inspection of the Cost of Living map in comparison to the Averages Wages map, there are some clear trends. California and DC Remain in the top 3 in both maps. However Hawaii has a much higher cost of living than compared to its average wage. This is most likely due to its status as a “vacation state”. The rest of the states are kind of ambiguous when looking at the choropleth map. Further inspection of the correlation will give us an idea of the relationship.
Testing Correlation in order to quantify the relationship
cor(State_avg_vs_COL$Index, State_avg_vs_COL$avgstatewages, use = "pairwise.complete.obs")
[1] 0.5894002
Here we see that there is a moderately strong positive correlation between the two variables. Intuitively this is not a surprising discovery, however I will make a correlation matrix to see which of the factors that contribute to the Cost of living carry more weight when looking at the average wage in each state.
Correlation Matrix
cor_matrix <- State_avg_vs_COL %>%
select(-State) %>%
cor(use = "pairwise.complete.obs")
cor_matrix <- round(cor_matrix, digits = 2)
meltCorMat <- melt(cor_matrix)
meltCorMat %>% ggplot(aes(x = Var1, y = Var2, fill = value)) + geom_tile(color = "white")+
scale_fill_gradient2(low = "blue", high = "red", mid = "white",
midpoint = 0, limit = c(-1,1), space = "Lab",
name="Pearson\nCorrelation") +
theme_minimal()+
theme(axis.text.x = element_text(angle = 45, vjust = 1,
size = 12, hjust = 1))+
coord_fixed() +
geom_text(aes(Var2, Var1, label = value), color = "black", size = 4)

Here we can see that the two biggest correlations other than The total cost of living index is the housing price index and a misc. index which I summarize to mean recreational activities and commodities such as eating out and entertainment systems.
Creating scatter map based on longitude and latitude
geo_prop <- list(scope = 'usa',
projection = list(type = 'albers usa'),
showland = TRUE,
showsubunits = TRUE,
landcolor = toRGB('gray10'),
showlakes = TRUE,
lakecolor = toRGB('white'))
plot_geo(wages,
lat = ~Lat,
lon = ~Long,
marker = list(size = wages$averageWage/15000),
text = wages$City) %>% layout(geo = geo_prop, title = "\nDensity and Intensity of the Average Wage for US Zip Codes")
No scattergeo mode specifed:
Setting the mode to markers
Read more about this attribute -> https://plotly.com/r/reference/#scatter-mode
No scattergeo mode specifed:
Setting the mode to markers
Read more about this attribute -> https://plotly.com/r/reference/#scatter-mode
The Map above shows all of the zip codes plotted via their latitude and longitude. The size or intensity of every point is proportional to how high the average wage in the zip code is. I just figured I would plot this because its a good looking graph and can express the range of zip codes left in the data after it had been cleaned.
Training A Linear Regression Model to Predict Average Wages In a Zip Code
Training the Model
LineWage <- wages %>% select(-State,-City,-ZipCodeType,-Zipcode)
set.seed(2)
split <- sample.split(LineWage,SplitRatio = 1/4)
train <- subset(LineWage, split = "TRUE")
test <- subset(LineWage, split = "FALSE")
model <- lm(averageWage~.,data = train)
summary(model)
Call:
lm(formula = averageWage ~ ., data = train)
Residuals:
Min 1Q Median 3Q Max
-34918 -3332 -717 1722 167551
Coefficients:
Estimate Std. Error t value Pr(>|t|)
(Intercept) 20594.1304072651 409.3614252313 50.308 < 0.0000000000000002 ***
Lat 29.3502510222 8.1391765199 3.606 0.000311 ***
Long 26.8223086918 2.8040993302 9.565 < 0.0000000000000002 ***
TaxReturnsFiled 0.3766591942 0.0677842110 5.557 0.0000000277 ***
EstimatedPopulation -1.3068716836 0.0397504079 -32.877 < 0.0000000000000002 ***
TotalWages 0.0000537401 0.0000003298 162.923 < 0.0000000000000002 ***
---
Signif. codes: 0 ‘***’ 0.001 ‘**’ 0.01 ‘*’ 0.05 ‘.’ 0.1 ‘ ’ 1
Residual standard error: 7090 on 28838 degrees of freedom
Multiple R-squared: 0.5078, Adjusted R-squared: 0.5077
F-statistic: 5951 on 5 and 28838 DF, p-value: < 0.00000000000000022
Testing the Model
predict <- predict(model, test)
Graphing for Accuracy
plot(predict, type = "l",col = "red") + lines(test$averageWage)
integer(0)

Calculating Root Mean Square Error for Accuracy
rmse <- sqrt(mean(predict-LineWage$averageWage)^2)
print(rmse)
[1] 0.00000000001681188
The error calculation is very low; this indicates a well trained model for future data.
LS0tDQp0aXRsZTogIldhZ2VzIFZpYSBaaXAgQ29kZSINCmF1dGhvcjogIkVsaWphaCBTaWxmaWVzIg0KZGF0ZTogIjExLzExLzIwMjEiDQpvdXRwdXQ6IGh0bWxfbm90ZWJvb2sNCi0tLQ0KIyMjIE9iamVjdGl2ZXMNCldoYXQgYXJlIHRoZSAxMDAgaGlnaGVzdCB0b3RhbCB3YWdlIHppcCBjb2Rlcz8gbG93ZXN0Pw0Kd2hhdCBhcmUgdGhlIDEwMCBoaWdoZXN0IGF2ZXJhZ2Ugd2FnZSB6aXAgY29kZXM/IGxvd2VzdD8NCmhvdyBkb2VzIHRoZSAxMDAgdG90YWwgd2FnZXMgb2YgdGhlIHppcCBjb2RlcyBkaWZmZXIgZnJvbSB0aGUgMTAwIGF2ZXJhZ2Ugd2FnZXMgb2YgdGhlIHppcGNvZGVzPw0KdG9wIDMgc3RhdGVzIHdpdGggdGhlIGhpZ2hlc3QvbG93ZXN0IHdhZ2VzPw0KTWFrZSBjbGVhciBjb25jaXNlIGdyYXBocyBhbmQgY29tcGFyZSB0byBvdGhlciB0eXBlcyBvZiBleGlzdGluZyBkYXRhDQpDcmVhdGUgYSBtYWNoaW5lIGxlYXJuaW5nIG1vZGVsIHRvIHByZWRpY3QgZnV0dXJlIGRhdGENCg0KDQoNCiMjIyBMb2FkIFRoZSBOZWNjZXNhcnkgbGlicmFyaWVzDQpgYGB7cn0NCmxpYnJhcnkodGlkeXZlcnNlKQ0KbGlicmFyeShtYXBzKQ0KbGlicmFyeShtYXBkYXRhKQ0KbGlicmFyeShwbG90bHkpDQpsaWJyYXJ5KGNhVG9vbHMpDQpsaWJyYXJ5KHJlc2hhcGUyKQ0KYGBgDQoNCiMjIyBFeGFtaW5lIGRhdGENCmBgYHtyfQ0Kd2FnZXMgPC0gcmVhZF9jc3YoImZyZWUtemlwY29kZS1kYXRhYmFzZS5jc3YiKQ0KZ2xpbXBzZSh3YWdlcykNCmBgYA0KDQojIyMgTG9va2luZyBhdCBVbml0ZWQgU3RhdGVzIG9ubHkNCmBgYHtyfQ0Kd2FnZXMgPC0gd2FnZXMgJT4lIGZpbHRlcihDb3VudHJ5ID09ICJVUyIpDQpgYGANCg0KIyMjIFJlbW92aW5nIHVud2FudGVkIHZhcmlhYmxlcw0KYGBge3J9DQp3YWdlcyA8LSB3YWdlcyAlPiUgc2VsZWN0KC1SZWNvcmROdW1iZXIsLVhheGlzLC1ZYXhpcywtWmF4aXMsLUxvY2F0aW9uLC1Xb3JsZFJlZ2lvbiwtTG9jYXRpb25UZXh0LC1Mb2NhdGlvblR5cGUsLUNvdW50cnksLURlY29tbWlzaW9uZWQsLU5vdGVzKQ0KYGBgDQoNCiMjIyBSZW1vdmluZyBVbndhbnRlZCBjYXNlcyBsaWtlIHB1ZXJ0byByaWNvIGFuZCBOQSB2YWx1ZXMgYW5kIGR1cGxpY2F0ZXMNCmBgYHtyfQ0KdW5pcXVlKHdhZ2VzJFN0YXRlKQ0KDQp3YWdlcyA8LSB3YWdlcyAlPiUgZmlsdGVyKFN0YXRlICE9ICJQUiIpDQoNCmZvcihpIGluIDE6OSl7DQogIHdhZ2VzPC0gd2FnZXMgJT4lIGZpbHRlcighaXMubmEod2FnZXNbaV0pKQ0KfQ0KDQp3YWdlcyA8LSB3YWdlcyAlPiUgZGlzdGluY3QoWmlwY29kZSwgLmtlZXBfYWxsID0gVFJVRSkNCmBgYA0KV2Ugc2VlIGEgbG9zcyBvZiBhYm91dCA1MCwwMDAgdmFsdWVzIGluIHRoZSBkYXRhIHNldC4gQWJvdXQgMjUsMDAwIGxvc3QgZnJvbSBibGFuayB2YWx1ZXMgYW5kIGFub3RoZXIgMjUsMDAwIGZyb20gZHVwbGljYXRlcy4gU2luY2UgdGhpcyBpcyB0aGUgbWFqb3JpdHkgb2YgdGhlIGRhdGEgc2V0LCBpdCBpcyBub3QgYSB2ZXJ5IGNsZWFuIGRhdGEgc2V0IGFuZCBtYXkgbm90IGhhdmUgYSB2ZXJ5IGFjY3VyYXRlIHJlcHJlc2VudGF0aW9uIG9mIHRoZSBpbml0aWFsIGRhdGEuIEhvd2V2ZXIsIEkgd2lsbCBzdGlsbCBwbGFuIHRvIGFuYWx5c2UgdGhlIGRhdGEgaW4gYSBjb21wcmVoZW5zaXZlIHdheSBpbiBvcmRlciB0byBmaW5kIGFuc3dlcnMgdG8gdGhlIHByb3Bvc2VkIHF1ZXN0aW9ucy4NCg0KIyMjIENyZWF0ZSBhbiBhdmVyYWdlIHdhZ2UgY29sdW1uIGJ5IGRpdmlkaW5nIHRoZSB0b3RhbCB3YWdlIGJ5IHRoZSBlc3RpbWF0ZWQgcG9wdWxhdGlvbiBhbmQgZm9ybWF0dGluZyBmb3IgdGhlIGRvbGxhciBhbW91bnRzDQpgYGB7cn0NCndhZ2VzIDwtIHdhZ2VzICU+JSBtdXRhdGUoYXZlcmFnZVdhZ2UgPSBmb3JtYXQocm91bmQoVG90YWxXYWdlcy9Fc3RpbWF0ZWRQb3B1bGF0aW9uLDIpLCBuc21hbGwgPSAyKSkNCndhZ2VzIDwtIHdhZ2VzICU+JSBtdXRhdGUoYXZlcmFnZVdhZ2UgPSBUb3RhbFdhZ2VzL0VzdGltYXRlZFBvcHVsYXRpb24pDQpgYGANCg0KIyMjIEVYcG9ydGluZyBhIGNzdiBmaWxlIGluIG9yZGVyIHRvIHVzZSB0aGUgY2xlYW5lZCBkYXRhIGluIG90aGVyIHByb2dyYW1zIGkuZS4gVGFibGVhdQ0KYGBge3J9DQp3cml0ZS50YWJsZSh3YWdlcyxmaWxlID0gIkNsZWFuV2FnZXMuY3N2Iixyb3cubmFtZXMgPSBGLHNlcCA9ICIsIikNCmBgYA0KDQoNCiMjIyBXaGF0IGlzIHRoZSAxMDAgaGlnaGVzdCB3YWdlIHppcCBjb2Rlcz8NCmBgYHtyfQ0Kb3B0aW9ucyhzY2lwZW4gPSA5OTkpDQpoaWdoZXN0d2FnZSA8LSB3YWdlcyAlPiUgYXJyYW5nZSgtVG90YWxXYWdlcykNCmhpZ2hlc3R3YWdlIDwtIGhpZ2hlc3R3YWdlICU+JSBtdXRhdGUocmFuayA9IDE6Mjg4NDQpDQpoaWdoZXN0d2FnZSA8LSBoaWdoZXN0d2FnZSAlPiUgZmlsdGVyKHJhbmsgPD0gMTAwKQ0KcHJpbnQoaGVhZChoaWdoZXN0d2FnZSkpDQpgYGANCg0KDQoNCmBgYHtyfQ0KDQpoaWdoZXN0d2FnZSAlPiUgZ2dwbG90KGFlcyh4ID0gU3RhdGUsZmlsbCA9IFN0YXRlKSkgKyBnZW9tX2JhcigpICsgbGFicyh0aXRsZSA9ICJOdW1iZXIgb2YgSGlnaCBXYWdlIFppcCBDb2RlcyBQZXIgU3RhdGUgaW4gdGhlIFRvcCAxMDAiICx4ID0gIlN0YXRlIix5ID0gIkZyZXF1ZW5jeSIpDQpgYGANCkxvb2tpbmcgYXQgdGhlIGdyYXBoIG9mIGVhY2ggc3RhdGUgaW4gdGhlIHRvcCAxMDAgYW5kIHRoZSBUb3RhbCB3YWdlcyBvZiB0aGUgemlwIGNvZGVzIGluIGVhY2ggc3RhdGUsIGl0IGlzIGVhc3kgdG8gc2VlIHRoYXQgQ2FsaWZvcm5pYSwgTmV3IFlvcmssIFRleGFzLCBhbmQgSWxsaW5vaXMgY29udHJpYnV0ZSB0aGUgbWFqb3JpdHkgb2YgdGhlIHppcCBjb2RlcyB3aXRoIHRoZSBoaWdoZXN0IHdhZ2VzLg0KDQpgYGB7cn0NCm9wdGlvbnMoc2NpcGVuID0gOTk5KQ0KaGlnaGVzdHdhZ2UgJT4lIGdncGxvdChhZXMoeCA9IFRvdGFsV2FnZXMvMTAwMDAwMDAwMCkpICsgZ2VvbV9kZW5zaXR5KCkgKyBsYWJzKHRpdGxlID0gIkRlbnNpdHkgb2YgVG90YWwgV2FnZXMgaW4gdGhlIFRvcCAxMDAiICx4ID0gIlRvdGFsIFdhZ2VzIChCaWxsaW9uICQpIix5ID0gIkRlbnNpdHkiKSANCmBgYA0KQnkgdGFraW5nIGEgbG9vayBhdCB0aGUgZ3JhcGggb2YgdGhlIGRlbnNpdHkgb2YgZWFjaCBwcmljZSB3ZSBjYW4gc2VlIHRoYXQgdGhlIG1ham9yaXR5IG9mIHppcCBjb2RlcyBpbiB0aGUgdG9wIDEwMCBoaWdoZXN0IHdhZ2VzIGFyZSBhcm91bmQgMS43IGJpbGxpb24gZG9sbGFycy4gQWxzbyBhcyB5b3UgZ2V0IHBhc3QgMS45IGJpbGxpb24gZG9sbGFycyB0aGUgYW1vdW50IG9mIHppcCBjb2RlcyBkZWNyZWFzZXMgc3RlYWRpbHkuDQoNCiMjIyBXaGF0IGlzIHRoZSAxMDAgbG93ZXN0IHdhZ2UgemlwIGNvZGVzPw0KYGBge3J9DQpsb3dlc3R3YWdlIDwtIHdhZ2VzICU+JSBhcnJhbmdlKFRvdGFsV2FnZXMpDQpsb3dlc3R3YWdlIDwtIGxvd2VzdHdhZ2UgJT4lIG11dGF0ZShyYW5rID0gMToyODg0NCkNCmxvd2VzdHdhZ2UgPC0gbG93ZXN0d2FnZSAlPiUgZmlsdGVyKHJhbmsgPD0gMTAwKQ0KcHJpbnQoaGVhZChsb3dlc3R3YWdlKSkNCmBgYA0KDQpgYGB7cn0NCmxvd2VzdHdhZ2UgJT4lIGdncGxvdChhZXMoeCA9IFN0YXRlLGZpbGwgPSBTdGF0ZSkpICsgZ2VvbV9iYXIoKSArIGxhYnModGl0bGUgPSAiTnVtYmVyIG9mIExvdyBXYWdlIFppcCBDb2RlcyBQZXIgU3RhdGUgaW4gdGhlIEJvdHRvbSAxMDAiICx4ID0gIlN0YXRlIix5ID0gIkZyZXF1ZW5jeSIpDQpgYGANCkhlcmUgd2Ugc2VlIHRoYXQgTWljaGlnYW4sIEFyaXpvbmEsIGFuZCBUZXhhcyBhcmUgdGhlIDMgbW9zdCBmcmVxdWVudCBzdGF0ZXMgaW4gdGhlIEJvdHRvbSAxMDAuDQoNCg0KYGBge3J9DQpvcHRpb25zKHNjaXBlbiA9IDk5OSkNCmxvd2VzdHdhZ2UgJT4lIGdncGxvdChhZXMoeCA9IFRvdGFsV2FnZXMvMTAwMDAwMCkpICsgZ2VvbV9kZW5zaXR5KCkgKyBsYWJzKHRpdGxlID0gIkRlbnNpdHkgb2YgVG90YWwgV2FnZXMgaW4gdGhlIEJvdHRvbSAxMDAiICx4ID0gIlRvdGFsIFdhZ2VzIChNaWxsaW9uICQpIix5ID0gIkRlbnNpdHkiKSANCmBgYA0KU2ltaWxhciB0byB0aGUgbGFzdCBkZW5zaXR5IGNoYXJ0LCB0aGlzIGdyYXBoIHByZWRpY3RzIHRoZSBtYWpvcml0eSBvZiB3YWdlcyBpbiB0aGUgYm90dG9tIDEwMCBhcmUgYXJvdW5kICQ0LjUgbWlsbGlvbi4NCg0KDQojIyMgV2hhdCBpcyB0aGUgMTAwIGhpZ2hlc3QgYXZlcmFnZSB3YWdlIHppcCBjb2Rlcz8NCmBgYHtyfQ0KaGlnaGVzdGF2Z3dhZ2UgPC0gd2FnZXMgJT4lIGFycmFuZ2UoLWF2ZXJhZ2VXYWdlKQ0KaGlnaGVzdGF2Z3dhZ2UgPC0gaGlnaGVzdGF2Z3dhZ2UgJT4lIG11dGF0ZShyYW5rID0gMToyODg0NCkNCmhpZ2hlc3Rhdmd3YWdlIDwtIGhpZ2hlc3Rhdmd3YWdlICU+JSBmaWx0ZXIocmFuayA8PSAxMDApDQpwcmludChoZWFkKGhpZ2hlc3Rhdmd3YWdlKSkNCmBgYA0KDQpgYGB7cn0NCmhpZ2hlc3Rhdmd3YWdlICU+JSBnZ3Bsb3QoYWVzKHggPSBTdGF0ZSxmaWxsID0gU3RhdGUpKSArIGdlb21fYmFyKCkgKyBsYWJzKHRpdGxlID0gIk51bWJlciBvZiBBdmVyYWdlIFdhZ2UgWmlwIENvZGVzIFBlciBTdGF0ZSBpbiB0aGUgVG9wIDEwMCIgLHggPSAiU3RhdGUiLHkgPSAiRnJlcXVlbmN5IikNCmBgYA0KVGhlIGF2ZXJhZ2UgemlwIGNvZGUgd2FnZSBpbiBlYWNoIHN0YXRlIHNob3dzIGEgZGlmZmVyZW50IHN0b3J5IHRoYW4gdGhlIHRvdGFsIHdhZ2VzIGluIGVhY2ggemlwIGNvZGUuIFRoZSB0d28gc3RhdGVzIHRoYXQgbWFrZSB1cCB0aGUgbWFqb3JpdHkgYXJlIE5ldyBZb3JrIGFuZCBDYWxpZm9ybmlhLCB3aGVyZSBOZXcgWW9yayBpcyBtdWNoIG1vcmUgZnJlcXVlbnQuDQoNCmBgYHtyfQ0Kb3B0aW9ucyhzY2lwZW4gPSA5OTkpDQpoaWdoZXN0YXZnd2FnZSAlPiUgZ2dwbG90KGFlcyh4ID0gYXZlcmFnZVdhZ2UvMTAwMCkpICsgZ2VvbV9kZW5zaXR5KCkgKyBsYWJzKHRpdGxlID0gIkRlbnNpdHkgb2YgQXZlcmFnZSBXYWdlcyBpbiB0aGUgVG9wIDEwMCIgLHggPSAiVG90YWwgV2FnZXMgKFRob3VzYW5kICQpIix5ID0gIkRlbnNpdHkiKSANCmBgYA0KVGhlIGRlbnNpdGllcyBzaG93IHRoYXQgdGhlIG1ham9yaXR5IG9mIHRoZSBhdmVyYWdlIHppcCBjb2RlIHdhZ2VzIGluIHRoZSB0b3AgMTAwIGFyZSBhcm91bmQgMTkwIHRob3VzYW5kIGRvbGxhcnMuIA0KDQojIyMgV2hhdCBpcyB0aGUgMTAwIGxvd2VzdCBhdmVyYWdlIHdhZ2UgemlwIGNvZGVzPw0KYGBge3J9DQpsb3dlc3Rhdmd3YWdlIDwtIHdhZ2VzICU+JSBhcnJhbmdlKGF2ZXJhZ2VXYWdlKQ0KbG93ZXN0YXZnd2FnZSA8LSBsb3dlc3Rhdmd3YWdlICU+JSBtdXRhdGUocmFuayA9IDE6Mjg4NDQpDQpsb3dlc3Rhdmd3YWdlIDwtIGxvd2VzdGF2Z3dhZ2UgJT4lIGZpbHRlcihyYW5rIDw9IDEwMCkNCnByaW50KGhlYWQobG93ZXN0YXZnd2FnZSkpDQpgYGANCg0KYGBge3J9DQpsb3dlc3Rhdmd3YWdlICU+JSBnZ3Bsb3QoYWVzKHggPSBTdGF0ZSxmaWxsID0gU3RhdGUpKSArIGdlb21fYmFyKCkgKyBsYWJzKHRpdGxlID0gIk51bWJlciBvZiBBdmVyYWdlIFdhZ2UgWmlwIENvZGVzIFBlciBTdGF0ZSBpbiB0aGUgQm90dG9tIDEwMCIgLHggPSAiU3RhdGUiLHkgPSAiRnJlcXVlbmN5IikNCmBgYA0KU2ltaWxhciB0byB0aGUgbG93ZXN0IHRvdGFsIHdhZ2VzLCBNaWNoaWdhbiBhbmQgQXJpem9uYSBhcmUgdGhlIGhpZ2hlc3QgY29udHJpYnV0b3JzIHRvIHRoZSBib3R0b20gMTAwIGF2ZXJhZ2UgemlwIGNvZGUgd2FnZXMuDQoNCg0KYGBge3J9DQpvcHRpb25zKHNjaXBlbiA9IDk5OSkNCmxvd2VzdGF2Z3dhZ2UgJT4lIGdncGxvdChhZXMoeCA9IGF2ZXJhZ2VXYWdlLzEwMDApKSArIGdlb21fZGVuc2l0eSgpICsgbGFicyh0aXRsZSA9ICJEZW5zaXR5IG9mIEF2ZXJhZ2UgV2FnZXMgaW4gdGhlIEJvdHRvbSAxMDAiICx4ID0gIlRvdGFsIFdhZ2VzIChUaG91c2FuZCAkKSIseSA9ICJEZW5zaXR5IikgDQpgYGANCkhlcmUgdGhlIG1ham9yaXR5IG9mIHRoZSBhdmVyYWdlIHdhZ2VzIGluIHRoZSBib3R0b20gMTAwIGFyZSBhcm91bmQgNy41IHRob3VzYW5kIGRvbGxhcnMuDQoNCiMjIyBXaGF0IGFyZSB0aGUgMyBzdGF0ZXMgd2l0aCB0aGUgaGlnaGVzdCB3YWdlcyBieSB0b3RhbD8NCmBgYHtyfQ0Kc3RhdGV3YWdlIDwtIHdhZ2VzICU+JSBncm91cF9ieShTdGF0ZSkgJT4lIHN1bW1hcmlzZSgNCiAgdG90YWxzdGF0ZXdhZ2VzID0gc3VtKFRvdGFsV2FnZXMpKQ0KaGlnaHN0YXRld2FnZSA8LSBzdGF0ZXdhZ2UgJT4lIGFycmFuZ2UoLXRvdGFsc3RhdGV3YWdlcykNCmhpZ2hzdGF0ZXdhZ2UgPC0gaGlnaHN0YXRld2FnZSAlPiUgbXV0YXRlKHJhbmsgPSAxOjUxKQ0KaGlnaHN0YXRld2FnZSA8LSBoaWdoc3RhdGV3YWdlICU+JSBmaWx0ZXIocmFuayA8PSAzKQ0KcHJpbnQoaGVhZChoaWdoc3RhdGV3YWdlKSkNCmBgYA0KVGhlc2UgcmVzdWx0cyBzdXBwb3J0IHRoZSBwcmV2aW91cyBjb25jbHVzaW9uIGZyb20gR3JhcGggMSB3aGVyZSB3ZSBzYXcgdGhhdCBDYWxpZm9ybmlhIFRleGFzIGFuZCBOZXcgWW9yayB3ZXJlIGFtb25nIHRoZSBtYWluIGNvbnRyaWJ1dG9ycyBvZiB0aGUgaGlnaGVzdCBzdGF0ZSB3YWdlcy4gSG93ZXZlciB0aGVzZSByZXN1bHRzIGdpdmUgdXMgZnVydGhlciBpbnNpZ2h0IHRoYXQgSWxsaW5vaXMgaXMgbm90IGFjdHVhbGx5IGFtb25nIHRoZSB0b3AgMyBoaWdoZXN0IHdhZ2Ugc3RhdGVzIHdoZW4gbG9va2luZyBhdCB0aGUgdG90YWwgd2FnZXMNCg0KDQojIyMgV2hhdCBhcmUgdGhlIDMgc3RhdGVzIHdpdGggdGhlIGxvd2VzdCB3YWdlcyBieSB0b3RhbD8NCmBgYHtyfQ0KbG93c3RhdGV3YWdlIDwtIHN0YXRld2FnZSAlPiUgYXJyYW5nZSh0b3RhbHN0YXRld2FnZXMpDQpsb3dzdGF0ZXdhZ2UgPC0gbG93c3RhdGV3YWdlICU+JSBtdXRhdGUocmFuayA9IDE6NTEpDQpsb3dzdGF0ZXdhZ2UgPC0gbG93c3RhdGV3YWdlICU+JSBmaWx0ZXIocmFuayA8PSAzKQ0KcHJpbnQoaGVhZChsb3dzdGF0ZXdhZ2UpKQ0KYGBgDQoNCg0KIyMjIFdoYXQgZG9lcyB0aGUgYXZlcmFnZSBvZiBlYWNoIHN0YXRlIGxvb2sgbGlrZT8NCmBgYHtyfQ0Kc3RhdGVfYXZnX3dhZ2UgPC0gd2FnZXMgJT4lIA0KICBncm91cF9ieShTdGF0ZSkgJT4lIA0KICBzdW1tYXJpc2UoYXZnc3RhdGV3YWdlcyA9IG1lYW4oVG90YWxXYWdlcykpDQoNCnBsb3RfZ2VvKGRhdGEgPSBzdGF0ZV9hdmdfd2FnZSwNCiAgICAgICAgICAgICAgICAgICAgICBsb2NhdGlvbm1vZGUgPSAnVVNBLXN0YXRlcycpICU+JSANCiAgYWRkX3RyYWNlKGxvY2F0aW9ucyA9IH5TdGF0ZSwNCiAgICAgICAgICAgIHogPSB+c3RhdGVfYXZnX3dhZ2UkYXZnc3RhdGV3YWdlcywNCiAgICAgICAgICAgIHptaW4gPSBtaW4oc3RhdGVfYXZnX3dhZ2UkYXZnc3RhdGV3YWdlcyksIA0KICAgICAgICAgICAgem1heCA9IG1heChzdGF0ZV9hdmdfd2FnZSRhdmdzdGF0ZXdhZ2VzKSwNCiAgICAgICAgICAgIGNvbG9yID0gc3RhdGVfYXZnX3dhZ2UkYXZnc3RhdGV3YWdlcykgJT4lIA0KICBsYXlvdXQoZ2VvID0gbGlzdChzY29wZT0gJ3VzYScpLA0KICAgICAgICAgdGl0bGUgPSAiXG5BdmVyYWdlIFdhZ2VzIGluIHRoZSBVbml0ZWQgU3RhdGVzIGJ5IFN0YXRlIikgJT4lIGNvbG9yYmFyKHRpY2twcmVmaXggPSAiJCIpDQpgYGANClRoZSBncmFwaCBhYm92ZSBzaG93cyB0aGF0IG91dCBvZiBhbGwgdGhlIHN0YXRlcywgdGhlIGhpZ2hlc3Qgd2FnZSBsb2NhdGlvbiBpcyBXYXNoaW5ndG9uIERDIHdpdGggYW4gYXZlcmFnZSB3YWdlIG9mIGFib3V0ICQ1NTAgTWlsbGlvbi4gVGhlIHNlY29uZCB0d28gbG9jYXRpb25zIGFyZSBDYWxpZm9ybmlhIGFuZCBOZXcgSmVyc2V5IGJ5IGEgJDE1MCBtaWxsaW9uIHdhZ2UgZ2FwLiBJbiBjb21wYXJpc29uIHRvIGFsbCBvdGhlciBMb2NhdGlvbnMsIHRoZXNlIHRocmVlIHN0YW5kIG91dCBhcyBzdGF0ZXMgYW5kIHRlcnJpdG9yaWVzIHdpdGggaGlnaCB3YWdlcy4NCg0KIyMjIFNob3dpbmcgdGhlIEhpZ2hlc3QgQXZlcmFnZSBXYWdlcyBpbiBUYWJ1bGFyIEZvcm0gDQpgYGB7cn0NCnN0YXRlX2F2Z193YWdlIDwtIHN0YXRlX2F2Z193YWdlICU+JSANCiAgYXJyYW5nZSgtYXZnc3RhdGV3YWdlcyApIA0KaGVhZChzdGF0ZV9hdmdfd2FnZSkNCmBgYA0KDQojIyMgSW1wb3J0aW5nIE90aGVyIERhdGFzZXRzIEZyb20gT25saW5lIEZvciBGdXJ0aGVyIEFuYWx5c2lzDQpgYGB7cn0NCkNvc3RPZkxpdmluZyA8LSByZWFkLmNzdigiQ29zdCBvZiBMaXZpbmcuY3N2IikNClN0YXRlQWJyZXYgPC0gcmVhZC5jc3YoIlN0YXRlQWJyZXYuY3N2IikNCmBgYA0KDQojIyMgUHJlcGFyaW5nIERhdGEgRm9yIEpvaW5pbmcNCmBgYHtyfQ0KU3RhdGVBYnJldiA8LSBTdGF0ZUFicmV2ICU+JSByZW5hbWUoU3RhdGUgPSBVU1BTLkFiYnJldmlhdGlvbikNCkNvc3RPZkxpdmluZyA8LSBDb3N0T2ZMaXZpbmcgJT4lIHJlbmFtZShTdGF0ZS5OYW1lID0gU3RhdGUpDQpgYGANCg0KIyMjIEpvaW5pbmcgdGhlIERhdGFzZXRzDQpgYGB7cn0NCkNvc3RPZkxpdmluZyA8LSBDb3N0T2ZMaXZpbmcgJT4lIGZ1bGxfam9pbihTdGF0ZUFicmV2LCBieSA9ICJTdGF0ZS5OYW1lIikNClN0YXRlX2F2Z192c19DT0wgPC0gc3RhdGVfYXZnX3dhZ2UgJT4lIGZ1bGxfam9pbihDb3N0T2ZMaXZpbmcsIGJ5ID0gIlN0YXRlIikNClN0YXRlX2F2Z192c19DT0wgPC0gU3RhdGVfYXZnX3ZzX0NPTCAlPiUgc2VsZWN0KC1SYW5rLC1TdGF0ZS5OYW1lKQ0KYGBgDQoNCiMjIyBXb3JraW5nIFdpdGggdGhlIE5ldyBEYXRhc2V0IFRvIERldGVybWluZSBQb3RlbnRpYWwgUmVsYXRpb25zaGlwcw0KYGBge3J9DQpwbG90X2dlbyhkYXRhID0gU3RhdGVfYXZnX3ZzX0NPTCwNCiAgICAgICAgICAgICAgICAgICAgICBsb2NhdGlvbm1vZGUgPSAnVVNBLXN0YXRlcycpICU+JSANCiAgYWRkX3RyYWNlKGxvY2F0aW9ucyA9IH5TdGF0ZSwNCiAgICAgICAgICAgIHogPSB+U3RhdGVfYXZnX3ZzX0NPTCRJbmRleCwNCiAgICAgICAgICAgIHptaW4gPSBtaW4oU3RhdGVfYXZnX3ZzX0NPTCRJbmRleCksIA0KICAgICAgICAgICAgem1heCA9IG1heChTdGF0ZV9hdmdfdnNfQ09MJEluZGV4KSwNCiAgICAgICAgICAgIGNvbG9yID0gU3RhdGVfYXZnX3ZzX0NPTCRJbmRleCkgJT4lIA0KICBsYXlvdXQoZ2VvID0gbGlzdChzY29wZT0gJ3VzYScpLA0KICAgICAgICAgdGl0bGUgPSAiXG5Db3N0IG9mIGxpdmluZyBJbmRleCBpbiB0aGUgVW5pdGVkIFN0YXRlcyBieSBTdGF0ZSIpDQpgYGANClVwb24gaW5zcGVjdGlvbiBvZiB0aGUgQ29zdCBvZiBMaXZpbmcgbWFwIGluIGNvbXBhcmlzb24gdG8gdGhlIEF2ZXJhZ2VzIFdhZ2VzIG1hcCwgdGhlcmUgYXJlIHNvbWUgY2xlYXIgdHJlbmRzLiBDYWxpZm9ybmlhIGFuZCBEQyBSZW1haW4gaW4gdGhlIHRvcCAzIGluIGJvdGggbWFwcy4gSG93ZXZlciBIYXdhaWkgaGFzIGEgbXVjaCBoaWdoZXIgY29zdCBvZiBsaXZpbmcgdGhhbiBjb21wYXJlZCB0byBpdHMgYXZlcmFnZSB3YWdlLiBUaGlzIGlzIG1vc3QgbGlrZWx5IGR1ZSB0byBpdHMgc3RhdHVzIGFzIGEgInZhY2F0aW9uIHN0YXRlIi4gVGhlIHJlc3Qgb2YgdGhlIHN0YXRlcyBhcmUga2luZCBvZiBhbWJpZ3VvdXMgd2hlbiBsb29raW5nIGF0IHRoZSBjaG9yb3BsZXRoIG1hcC4gRnVydGhlciBpbnNwZWN0aW9uIG9mIHRoZSBjb3JyZWxhdGlvbiB3aWxsIGdpdmUgdXMgYW4gaWRlYSBvZiB0aGUgcmVsYXRpb25zaGlwLg0KDQojIyMgVGVzdGluZyBDb3JyZWxhdGlvbiBpbiBvcmRlciB0byBxdWFudGlmeSB0aGUgcmVsYXRpb25zaGlwDQpgYGB7cn0NCmNvcihTdGF0ZV9hdmdfdnNfQ09MJEluZGV4LCBTdGF0ZV9hdmdfdnNfQ09MJGF2Z3N0YXRld2FnZXMsIHVzZSA9ICJwYWlyd2lzZS5jb21wbGV0ZS5vYnMiKQ0KYGBgDQpIZXJlIHdlIHNlZSB0aGF0IHRoZXJlIGlzIGEgbW9kZXJhdGVseSBzdHJvbmcgcG9zaXRpdmUgY29ycmVsYXRpb24gYmV0d2VlbiB0aGUgdHdvIHZhcmlhYmxlcy4gSW50dWl0aXZlbHkgdGhpcyBpcyBub3QgYSBzdXJwcmlzaW5nIGRpc2NvdmVyeSwgaG93ZXZlciBJIHdpbGwgbWFrZSBhIGNvcnJlbGF0aW9uIG1hdHJpeCB0byBzZWUgd2hpY2ggb2YgdGhlIGZhY3RvcnMgdGhhdCBjb250cmlidXRlIHRvIHRoZSBDb3N0IG9mIGxpdmluZyBjYXJyeSBtb3JlIHdlaWdodCB3aGVuIGxvb2tpbmcgYXQgdGhlIGF2ZXJhZ2Ugd2FnZSBpbiBlYWNoIHN0YXRlLg0KDQojIyMgQ29ycmVsYXRpb24gTWF0cml4DQpgYGB7cn0NCmNvcl9tYXRyaXggPC0gU3RhdGVfYXZnX3ZzX0NPTCAlPiUNCiAgc2VsZWN0KC1TdGF0ZSkgJT4lDQogIGNvcih1c2UgPSAicGFpcndpc2UuY29tcGxldGUub2JzIikNCmNvcl9tYXRyaXggPC0gcm91bmQoY29yX21hdHJpeCwgZGlnaXRzID0gMikNCg0KbWVsdENvck1hdCA8LSBtZWx0KGNvcl9tYXRyaXgpDQoNCm1lbHRDb3JNYXQgJT4lIGdncGxvdChhZXMoeCA9IFZhcjEsIHkgPSBWYXIyLCBmaWxsID0gdmFsdWUpKSArIGdlb21fdGlsZShjb2xvciA9ICJ3aGl0ZSIpKw0KIHNjYWxlX2ZpbGxfZ3JhZGllbnQyKGxvdyA9ICJibHVlIiwgaGlnaCA9ICJyZWQiLCBtaWQgPSAid2hpdGUiLCANCiAgIG1pZHBvaW50ID0gMCwgbGltaXQgPSBjKC0xLDEpLCBzcGFjZSA9ICJMYWIiLCANCiAgIG5hbWU9IlBlYXJzb25cbkNvcnJlbGF0aW9uIikgKw0KICB0aGVtZV9taW5pbWFsKCkrIA0KIHRoZW1lKGF4aXMudGV4dC54ID0gZWxlbWVudF90ZXh0KGFuZ2xlID0gNDUsIHZqdXN0ID0gMSwgDQogICAgc2l6ZSA9IDEyLCBoanVzdCA9IDEpKSsNCiBjb29yZF9maXhlZCgpICsNCiAgZ2VvbV90ZXh0KGFlcyhWYXIyLCBWYXIxLCBsYWJlbCA9IHZhbHVlKSwgY29sb3IgPSAiYmxhY2siLCBzaXplID0gNCkNCmBgYA0KSGVyZSB3ZSBjYW4gc2VlIHRoYXQgdGhlIHR3byBiaWdnZXN0IGNvcnJlbGF0aW9ucyBvdGhlciB0aGFuIFRoZSB0b3RhbCBjb3N0IG9mIGxpdmluZyBpbmRleCBpcyB0aGUgaG91c2luZyBwcmljZSBpbmRleCBhbmQgYSBtaXNjLiBpbmRleCB3aGljaCBJIHN1bW1hcml6ZSB0byBtZWFuIHJlY3JlYXRpb25hbCBhY3Rpdml0aWVzIGFuZCBjb21tb2RpdGllcyBzdWNoIGFzIGVhdGluZyBvdXQgYW5kIGVudGVydGFpbm1lbnQgc3lzdGVtcy4NCg0KIyMjIENyZWF0aW5nIHNjYXR0ZXIgbWFwIGJhc2VkIG9uIGxvbmdpdHVkZSBhbmQgbGF0aXR1ZGUNCmBgYHtyfQ0KZ2VvX3Byb3AgPC0gbGlzdChzY29wZSA9ICd1c2EnLA0KICAgICAgICAgICAgICAgICBwcm9qZWN0aW9uID0gbGlzdCh0eXBlID0gJ2FsYmVycyB1c2EnKSwgDQogICAgICAgICAgICAgICAgIHNob3dsYW5kID0gVFJVRSwNCiAgICAgICAgICAgICAgICAgc2hvd3N1YnVuaXRzID0gVFJVRSwNCiAgICAgICAgICAgICAgICAgbGFuZGNvbG9yID0gdG9SR0IoJ2dyYXkxMCcpLA0KICAgICAgICAgICAgICAgICBzaG93bGFrZXMgPSBUUlVFLCANCiAgICAgICAgICAgICAgICAgbGFrZWNvbG9yID0gdG9SR0IoJ3doaXRlJykpDQoNCnBsb3RfZ2VvKHdhZ2VzLCANCiAgICAgICAgbGF0ID0gfkxhdCwNCiAgICAgICAgbG9uID0gfkxvbmcsDQogICAgICAgIG1hcmtlciA9IGxpc3Qoc2l6ZSA9IHdhZ2VzJGF2ZXJhZ2VXYWdlLzE1MDAwKSwNCiAgICAgICAgdGV4dCA9IHdhZ2VzJENpdHkpICU+JSBsYXlvdXQoZ2VvID0gZ2VvX3Byb3AsIHRpdGxlID0gIlxuRGVuc2l0eSBhbmQgSW50ZW5zaXR5IG9mIHRoZSBBdmVyYWdlIFdhZ2UgZm9yIFVTIFppcCBDb2RlcyIpDQpgYGANClRoZSBNYXAgYWJvdmUgc2hvd3MgYWxsIG9mIHRoZSB6aXAgY29kZXMgcGxvdHRlZCB2aWEgdGhlaXIgbGF0aXR1ZGUgYW5kIGxvbmdpdHVkZS4gVGhlIHNpemUgb3IgaW50ZW5zaXR5IG9mIGV2ZXJ5IHBvaW50IGlzIHByb3BvcnRpb25hbCB0byBob3cgaGlnaCB0aGUgYXZlcmFnZSB3YWdlIGluIHRoZSB6aXAgY29kZSBpcy4gSSBqdXN0IGZpZ3VyZWQgSSB3b3VsZCBwbG90IHRoaXMgYmVjYXVzZSBpdHMgYSBnb29kIGxvb2tpbmcgZ3JhcGggYW5kIGNhbiBleHByZXNzIHRoZSByYW5nZSBvZiB6aXAgY29kZXMgbGVmdCBpbiB0aGUgZGF0YSBhZnRlciBpdCBoYWQgYmVlbiBjbGVhbmVkLg0KDQoNCg0KIyMgVHJhaW5pbmcgQSBMaW5lYXIgUmVncmVzc2lvbiBNb2RlbCB0byBQcmVkaWN0IEF2ZXJhZ2UgV2FnZXMgSW4gYSBaaXAgQ29kZQ0KDQojIyMgVHJhaW5pbmcgdGhlIE1vZGVsDQpgYGB7cn0NCkxpbmVXYWdlIDwtIHdhZ2VzICU+JSBzZWxlY3QoLVN0YXRlLC1DaXR5LC1aaXBDb2RlVHlwZSwtWmlwY29kZSkNCg0Kc2V0LnNlZWQoMikNCnNwbGl0IDwtIHNhbXBsZS5zcGxpdChMaW5lV2FnZSxTcGxpdFJhdGlvID0gMS80KQ0KdHJhaW4gPC0gc3Vic2V0KExpbmVXYWdlLCBzcGxpdCA9ICJUUlVFIikNCnRlc3QgPC0gc3Vic2V0KExpbmVXYWdlLCBzcGxpdCA9ICJGQUxTRSIpDQoNCm1vZGVsIDwtIGxtKGF2ZXJhZ2VXYWdlfi4sZGF0YSA9IHRyYWluKQ0Kc3VtbWFyeShtb2RlbCkNCmBgYA0KDQojIyMgVGVzdGluZyB0aGUgTW9kZWwNCmBgYHtyfQ0KcHJlZGljdCA8LSBwcmVkaWN0KG1vZGVsLCB0ZXN0KQ0KYGBgDQoNCiMjIyBHcmFwaGluZyBmb3IgQWNjdXJhY3kNCmBgYHtyfQ0KcGxvdChwcmVkaWN0LCB0eXBlID0gImwiLGNvbCA9ICJyZWQiKSArIGxpbmVzKHRlc3QkYXZlcmFnZVdhZ2UpDQpgYGANCg0KIyMjIENhbGN1bGF0aW5nIFJvb3QgTWVhbiBTcXVhcmUgRXJyb3IgZm9yIEFjY3VyYWN5DQpgYGB7cn0NCnJtc2UgPC0gc3FydChtZWFuKHByZWRpY3QtTGluZVdhZ2UkYXZlcmFnZVdhZ2UpXjIpDQpwcmludChybXNlKQ0KYGBgDQpUaGUgZXJyb3IgY2FsY3VsYXRpb24gaXMgdmVyeSBsb3c7IHRoaXMgaW5kaWNhdGVzIGEgd2VsbCB0cmFpbmVkIG1vZGVsIGZvciBmdXR1cmUgZGF0YS4NCg0KDQojIyMgU291cmNlcw0KDQoxLiBLYWdnbGU6ICAgaHR0cHM6Ly93d3cua2FnZ2xlLmNvbS9wYXZhbnNhbmFnYXBhdGkvdXMtd2FnZXMtdmlhLXppcGNvZGUNCjIuIFlvdXJEaWN0aW9uYXJ5OiAgIGh0dHBzOi8vYWJicmV2aWF0aW9ucy55b3VyZGljdGlvbmFyeS5jb20vYXJ0aWNsZXMvc3RhdGUtYWJicmV2Lmh0bWwNCjMuIE1FUklDOiAgIGh0dHBzOi8vbWVyaWMubW8uZ292L2RhdGEvY29zdC1saXZpbmctZGF0YS1zZXJpZXMNCg==